package net.risesoft.service.identity.impl;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

import net.risesoft.consts.CacheNameConsts;
import net.risesoft.entity.Y9Person;
import net.risesoft.entity.identity.Y9IdentityToResourceAndAuthorityBase;
import net.risesoft.entity.identity.person.Y9PersonToResourceAndAuthority;
import net.risesoft.entity.permission.Y9Authorization;
import net.risesoft.enums.ResourceTypeEnum;
import net.risesoft.manager.Y9OrgBaseManager;
import net.risesoft.manager.Y9PersonToResourceAndAuthorityManager;
import net.risesoft.repository.identity.person.Y9PersonToResourceAndAuthorityRepository;
import net.risesoft.service.identity.Y9PersonToResourceAndAuthorityService;
import net.risesoft.y9public.entity.resource.Y9ResourceBase;

/**
 * @author dingzhaojun
 * @author qinman
 * @author mengjuhua
 * @date 2022/2/10
 */
@Transactional(value = "rsTenantTransactionManager", readOnly = true)
@CacheConfig(cacheNames = CacheNameConsts.PERSONS_TO_RESOURCES)
@Service
@RequiredArgsConstructor
public class Y9PersonToResourceAndAuthorityServiceImpl implements Y9PersonToResourceAndAuthorityService {
    
    private final Y9PersonToResourceAndAuthorityRepository y9PersonToResourceAndAuthorityRepository;

    private final Y9OrgBaseManager y9OrgBaseManager;
    private final Y9PersonToResourceAndAuthorityManager y9PersonToResourceAndAuthorityManager;
    
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Map<Object, Boolean> seen = new ConcurrentHashMap<>();
        return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
    }

    @Transactional(readOnly = false)
    @Override
    public void deleteByAppId(String appId) {
        y9PersonToResourceAndAuthorityRepository.deleteByAppId(appId);
    }

    @Transactional(readOnly = false)
    @Override
    public void deleteByAuthorizationId(String authorizationId) {
        y9PersonToResourceAndAuthorityManager.deleteByAuthorizationId(authorizationId);
    }

    @Transactional(readOnly = false)
    @Override
    public void deleteByAuthorizationIdAndOrgUnitId(String authorizationId, String orgUnitId) {
        List<Y9Person> allPersons = y9OrgBaseManager.listAllPersonsRecursionDownward(orgUnitId);
        for (Y9Person y9Person : allPersons) {
            deleteByAuthorizationIdAndPersonId(authorizationId, y9Person.getId());
        }
    }
    
    @Transactional(readOnly = false)
    @Override
    public void deleteByAuthorizationIdAndPersonId(String authorizationId, String personId) {
        y9PersonToResourceAndAuthorityRepository.deleteByAuthorizationIdAndPersonId(authorizationId, personId);
    }

    @Transactional(readOnly = false)
    @Override
    public void deleteByOrgUnitId(String orgUnitId) {
        List<Y9Person> personList = y9OrgBaseManager.listAllPersonsRecursionDownward(orgUnitId);
        for (Y9Person y9Person : personList) {
            deleteByPersonId(y9Person.getId());
        }
    }

    @Transactional(readOnly = false)
    @Override
    public void deleteByPersonId(String personId) {
        y9PersonToResourceAndAuthorityRepository.deleteByPersonId(personId);
    }

    @Transactional(readOnly = false)
    @Override
    public void deleteByResourceId(String resourceId) {
        y9PersonToResourceAndAuthorityRepository.deleteByResourceId(resourceId);
    }

    @Override
    public boolean hasPermission(String personId, String resourceId, Integer authority) {
        return !y9PersonToResourceAndAuthorityRepository.findByPersonIdAndResourceIdAndAuthority(personId, resourceId, authority).isEmpty();
    }

    @Override
    public boolean hasPermissionByCustomId(String personId, String resourceCustomId, Integer authority) {
        return !y9PersonToResourceAndAuthorityRepository.findByPersonIdAndResourceCustomIdAndAuthority(personId, resourceCustomId, authority).isEmpty();
    }

    @Override
    public List<Y9PersonToResourceAndAuthority> list(String personId, String parentResourceId, Integer authority) {
        return y9PersonToResourceAndAuthorityRepository.findByPersonIdAndParentResourceIdAndAuthorityOrderByResourceTabIndex(personId, parentResourceId, authority);
    }

    @Override
    public List<Y9PersonToResourceAndAuthority> list(String personId, String parentResourceId, Integer resourceType, Integer authority, boolean distinctResourceId) {
        List<Y9PersonToResourceAndAuthority> list = y9PersonToResourceAndAuthorityRepository.findByPersonIdAndParentResourceIdAndAuthorityAndResourceTypeOrderByResourceTabIndex(personId, parentResourceId, authority, resourceType);
        if (distinctResourceId) {
            list = list.stream().filter(distinctByKey(Y9IdentityToResourceAndAuthorityBase::getResourceId)).collect(Collectors.toList());
        }
        return list;
    }

    @Override
    public List<Y9PersonToResourceAndAuthority> listAppsByAuthority(String personId, Integer authority, boolean distinctResourceId) {
        List<Y9PersonToResourceAndAuthority> list = y9PersonToResourceAndAuthorityRepository.findByPersonIdAndAuthorityAndResourceTypeOrderByResourceTabIndex(personId, authority, ResourceTypeEnum.APP.getValue());
        if (distinctResourceId) {
            list = list.stream().filter(distinctByKey(Y9IdentityToResourceAndAuthorityBase::getResourceId)).collect(Collectors.toList());
        }
        return list;
    }

    @Transactional(readOnly = false)
    @Override
    public void saveOrUpdate(Y9ResourceBase y9ResourceBase, Y9Person person, Y9Authorization y9Authorization, Boolean inherit) {
        y9PersonToResourceAndAuthorityManager.saveOrUpdate(y9ResourceBase, person, y9Authorization, inherit);
    }

    @Transactional(readOnly = false)
    @Override
    public void update(String resourceId, String resourceName, String systemName, String systemCnName, String description) {
        List<Y9PersonToResourceAndAuthority> list = y9PersonToResourceAndAuthorityRepository.findByResourceId(resourceId);
        List<Integer> ids = list.stream().map(Y9PersonToResourceAndAuthority::getId).collect(Collectors.toList());
        for (Integer id : ids) {
            Y9PersonToResourceAndAuthority personResourceMapping = y9PersonToResourceAndAuthorityRepository.findById(id).orElse(null);
            personResourceMapping.setResourceName(resourceName);
            personResourceMapping.setSystemCnName(systemCnName);
            personResourceMapping.setSystemName(systemName);
            personResourceMapping.setResourceDescription(description);
            y9PersonToResourceAndAuthorityRepository.save(personResourceMapping);
        }
    }

}
